classdef XmlParser
    %XMLPARSER 此处显示有关此类的摘要
    %   此处显示详细说明
    
    properties
        m_cstr
        m_cnt
        m_MAX_MEM
    end
    
    methods
        function obj = XmlParser(cstr, varargin)
            %XMLPARSER 构造此类的实例
            %   此处显示详细说明
            obj.m_cstr = cstr;
            obj.m_cnt = 1;
            obj.m_MAX_MEM = 1000;
            if nargin == 2
                obj.m_MAX_MEM = varargin{1};
            end
        end
        
        function obj = skipWhiteSpace(obj)
            % 跳过空白字符
            while obj.m_cnt < length(obj.m_cstr) && isWhiteSpace(obj.m_cstr(obj.m_cnt))
                obj.m_cnt = obj.m_cnt + 1;
            end
        end

        function obj = skipDeclaration(obj)
            % 跳过xml声明
            if ~isequal(obj.m_cstr(obj.m_cnt:(obj.m_cnt+4)), '<?xml')
                return;
            end
            obj.m_cnt = obj.m_cnt + 5;
            while obj.m_cstr(obj.m_cnt) ~= '>'
                obj.m_cnt = obj.m_cnt + 1;
            end
            obj.m_cnt = obj.m_cnt + 1;
        end

        function [obj, elem] = parse(obj)
            obj.m_cnt = 1;
            obj = obj.skipDeclaration();
            obj = obj.skipWhiteSpace();
            [obj, elem] = obj.parseElement();
        end
        
        function [obj, elem] = parseElement(obj)
            elem = XmlElement('tmp', '', obj.m_MAX_MEM);
            obj = obj.skipWhiteSpace();

            % 解析tag名
            pos = obj.m_cnt;
            
            if obj.m_cstr(pos) == '<' && isValidTagName(obj.m_cstr(pos+1),1)
                obj.m_cnt = obj.m_cnt + 1;
                [obj, tag] = obj.parseElementTag();
                elem.m_tag = tag;
            else
                error('parse element tag error');
            end
            
            while obj.m_cnt <= length(obj.m_cstr)
                obj = obj.skipWhiteSpace();
                if obj.m_cstr(obj.m_cnt) == '/'
                    if obj.m_cstr(obj.m_cnt + 1) == '>'
                        obj.m_cnt = obj.m_cnt + 2;
                        break;
                    else
                        error('xml empty element is error');
                    end
                elseif obj.m_cstr(obj.m_cnt) == '>'
                    % 解析内容
                    obj.m_cnt = obj.m_cnt + 1;
                    obj = obj.skipWhiteSpace();
                    [obj, content] = obj.parseElementContent();
                    if ~isempty(content)
                        elem.m_content = content;
                    end

                elseif obj.m_cstr(obj.m_cnt) == '<'
                    if obj.m_cstr(obj.m_cnt + 1) == '/'
                        % </ + tag_len + >
                        end_tag_len = 2 + length(elem.m_tag) + 1;
                        
                        if isequal(obj.m_cstr(obj.m_cnt:(obj.m_cnt + end_tag_len - 1)), ['</', elem.m_tag, '>'])
                            obj.m_cnt = obj.m_cnt + end_tag_len;
                        else
                            error('XML element %s end tag not found', elem.m_tag);
                        end
                        break;
                    else
                        [obj, sub_elem] = obj.parseElement();
                        elem = elem.appendChild(sub_elem);
                    end
                else % 属性
                    [obj, attrKey] = obj.parseElementAttrKey();
                    obj = obj.skipWhiteSpace();
                    if obj.m_cstr(obj.m_cnt) ~= '='
                        error('xml element attr is error: %s', attrKey);
                    end
                    obj.m_cnt = obj.m_cnt + 1;
                    obj = obj.skipWhiteSpace();
                    [obj, attrValue] = obj.parseElementAttrValue();
                    elem = elem.appendAttri(attrKey, attrValue);
                end

            end
            
        end

        function [obj, tag] = parseElementTag(obj)
            % 解析tag名
            if isValidTagName(obj.m_cstr(obj.m_cnt),1)
                pos = obj.m_cnt;
                while isValidTagName(obj.m_cstr(pos))
                    pos = pos + 1;
                end
            
                tag = obj.m_cstr(obj.m_cnt:(pos-1));
                obj.m_cnt = pos;
            else
                error('parse element tag error');
            end
        end
        
        function [obj, content] = parseElementContent(obj)
            pos = obj.m_cnt;
            while obj.m_cstr(pos) ~= '<'
                pos = pos + 1;
            end
            content = obj.m_cstr(obj.m_cnt:(pos-1));
            obj.m_cnt = pos;
        end
        
        function [obj, attrKey] = parseElementAttrKey(obj)
            % 解析属性名
            if isValidTagName(obj.m_cstr(obj.m_cnt),1)
                pos = obj.m_cnt;
                while isValidTagName(obj.m_cstr(pos))
                    pos = pos + 1;
                end
            
                attrKey = obj.m_cstr(obj.m_cnt:(pos-1));
                obj.m_cnt = pos;
            else
                error('parse element attributes error');
            end
        end
        
        function [obj, attrValue] = parseElementAttrValue(obj)
            % 解析属性值
            if obj.m_cstr(obj.m_cnt) ~= '"'
                error("xml element attr value should be in double quotes");
            end
           
            obj.m_cnt = obj.m_cnt + 1;
            
            pos = obj.m_cnt;
            while (obj.m_cstr(pos) ~= '"')
                pos = pos + 1;
                if pos > length(obj.m_cstr)
                    error("xml element attr value should be in double quotes");
                end
            end
            attrValue = obj.m_cstr(obj.m_cnt:(pos-1));
            pos = pos + 1;
            obj.m_cnt = pos;
        end
    end
end

function res = isWhiteSpace(ch)
    ascii = double(ch);
    res = (ascii == 13) || (ascii == 10) || (ascii == 9) || (ascii == 32);
end

function res = isValidTagName(ch, varargin)
    if nargin == 2
        % 变量名首符号只能是字符或下划线
        if varargin{1}
            res = isletter(ch) || ch == '_';
            return;
        end
    end

    res = isstrprop(ch, 'alphanum') || ch == '_' || ch == '-' ...
        || ch == ':' || ch == '.';

end


